import { Helper } from "./helper";

class ooo {
  public Data: number[];
  public totalbytes: number;
  public used: number;
  public used_one: number;
  public size: number;
}
export class uint_buf {
  public constructor(o: ooo) {
    this.Oo = o;
  }
  public Oo: ooo;
  public SetByte(value: number, position: number) {
    this.Oo.Data[position >> 2] |= Helper.uint(value << (((position & 0b11) << 3) & 255));
  }
  public ToNext() {
    this.Oo.used++;
    this.Oo.used_one = 0;
  }
  public * Push(bytes: number[], offset: number, length: number, offset_last: number) {
    let i = offset;
    let x, y;

    do {
      x = this.Oo.used_one;
      if (x != 0)
      {
          x = (0b11 - x);
          var max = x - length + 1;
          if (max < 0) max = 0;
          for (y = this.Oo.used_one; x > max; x--, y++, i++)
          {
              this.Oo.Data[this.Oo.used] |= Helper.uint((bytes[i] << ((y^3) << 3)));
              this.Oo.used_one++;
              if (length == i) break;
          }
          if (x != 0) break;
          this.Oo.Data[this.Oo.used] |= bytes[i++];
          this.Oo.used_one = 0;
          this.Oo.used++;
          if (this.Oo.used >= this.Oo.size - offset_last)
          {
              this.Oo.used = 0;
              yield i;
          }
      }
      else if (this.Oo.totalbytes != 0 && this.Oo.used >= this.Oo.size - offset_last) {
        this.Oo.used = 0;
        yield i;
      }
      this.Oo.totalbytes += length;
      this.Oo.used_one = this.Oo.totalbytes & 0b11;

      for (; i + 3 < length;) {
        this.Oo.Data[this.Oo.used] = Helper.uint((bytes[i] << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | bytes[i + 3]);
        this.Oo.used++;
        i += 4;
        if (this.Oo.used >= this.Oo.size - offset_last) {
          this.Oo.used = 0;
          yield i;
        }
      }

      x = this.Oo.totalbytes & 0b11;
      if (x != 0) {
        this.Oo.Data[this.Oo.used] = 0;
        for (y = 3; x > 0; x--, y--, i++) {
          this.Oo.Data[this.Oo.used] |= Helper.uint(bytes[i] << (y << 3));
          if (length == i) break;
        }
      }
    }
    while (false);
  }
  public PushNext(b: number) {
    if ((this.Oo.totalbytes & 0b11) != 0) this.Oo.Data[this.Oo.used] |= Helper.uint(b << (((3 - this.Oo.totalbytes & 0b11) << 3) & 0xff));
    else this.Oo.Data[this.Oo.used] = Helper.uint(b << 24);
    this.Oo.used++;
    this.Oo.used_one = 0;
    if (this.Oo.used >= this.Oo.size) this.Oo.used = 0;
  }
  public PushTotal() {
    this.Oo.Data[this.Oo.used++] = Helper.uint(this.Oo.totalbytes >>> 29);
    this.Oo.Data[this.Oo.used++] = Helper.uint(this.Oo.totalbytes << 3);
    this.Oo.used_one = 0;
    if (this.Oo.used > this.Oo.size) this.Oo.used = 0;
  }
  public PushTotalReverse() {
    this.Oo.Data[this.Oo.used++] = Helper.uint(this.Oo.totalbytes >>> 3);
    this.Oo.Data[this.Oo.used++] = Helper.uint(this.Oo.totalbytes << 29);
    this.Oo.used_one = 0;
    if (this.Oo.used > this.Oo.size) this.Oo.used = 0;
  }
  public Fill(x: number, offset_last: number) {
    for (; this.Oo.used < this.Oo.size - offset_last; this.Oo.used++) this.Oo.Data[this.Oo.used] = x;
    this.Oo.used_one = 0;
    if (this.Oo.used >= this.Oo.size) this.Oo.used = 0;
  }
  public PushNext4(x: number) {
    this.Oo.Data[this.Oo.used++] = x;
    this.Oo.used_one = 0;
    if (this.Oo.used >= this.Oo.size) this.Oo.used = 0;
  }
  public IsFULL(offset: number): boolean {
    if (this.Oo.used == 0) return this.Oo.totalbytes > 3;
    else {
      let r: boolean;
      if (this.Oo.used + offset == this.Oo.size) r = this.Oo.used_one != 0;
      else r = this.Oo.used + offset > this.Oo.size;
      if (r) this.Fill(0, 0);
      return r;
    }
  }
  public get Used(): number {
    return this.Oo.used;
  }
  public get UsedBytes(): number {
    return (this.Oo.used << 2) + this.Oo.used_one;
  }

  public static New(size: number) {
    let buf = new ooo();
    buf.Data = new Array(size);
    buf.size = size;
    buf.totalbytes = 0;
    buf.used = 0;
    buf.used_one = 0;
    return new uint_buf(buf);
  }
}